/*
 * Created on Oct 24, 2006
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.inibb.app.sftp.impl.jsch;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JPasswordField;

import com.inibb.app.sftp.RemoteFileEntry;
import com.inibb.app.sftp.SFTPAuthInfo;
import com.inibb.app.sftp.exception.SFTPAuthException;
import com.inibb.app.sftp.exception.SFTPException;
import com.inibb.app.sftp.exception.SFTPIOException;
import com.inibb.app.sftp.exception.SFTPRemoteFileNotFoundException;
import com.inibb.app.sftp.exception.SFTPRemoteIOException;




/**
 * @author 838474
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class OpenSSHDirListParser {

	private InetAddress connectedAddress = null;
	private int connectedPort = 0;
	private SFTPAuthInfo connectedAuthInfo = null;
	
	private String cmdStringBase = null;
	private boolean passwordLogin = false;
	
	private String lsOptions = "-l";
	private String lsCommand = "ls";
	private String sshCommand = "ssh";
	private static final String PASSWORD_PROMPT = "Password Authentication";
	private static final String FINGERPRINT_PROMPT = "list of known hosts";
	private static final String FILE_NOT_FOUND_PROMPT = "does not exist";
	private static final String HOST_NOT_FOUND_PROMPT = "no address associated with name";
	
	private static int DIR_STARTS_BELOW = 0;
	private static int PASSWORD_PROMPT_BELOW = 1;
	private static int FINGERPRINT_PROMPT_BELOW =2;
	private static int FILE_NOT_FOUND_PROMPT_BELOW =3;
	private static int HOST_NOT_FOUND_PROMPT_BELOW =4;
	private static int UNKNOWN_LINE = 99;
	
	private static RemoteFileEntry parseLs (String realPath, String lsLine, boolean isListingDir) {
//		String lines[] = lsOutput.split("\n");
//		List fileList = new ArrayList();
		boolean isDir = false;
		boolean isLink = false;
		long lastUpdate = 0;
		//Date sysdate = Sys.getSysDate();
		Date sysdate = new Date();
//		fileLoop : for (int i = 0; i < lines.length; i++ ) {
			String year = "";
			String month = "";
			String day = "";
			String time = "";
			int numOfLink = -1;
			String user = "";
			String group = "";
			int filesize = -1;
			String filename = "";
			String tokens[] = lsLine.split("[\\s]+");
			for (int j = 0; j < tokens.length; j++) {
				switch (j) {
					case 0 :
					    if (tokens[j].startsWith("d")) {
					        isDir = true;
					    } else if (tokens[j].startsWith("l")) {
					        isLink = true;
					    } else if (tokens[j].startsWith("-")) {
					        isDir = false;
					        isLink = false;
					    } else {
					    	return null;
					    }
						break;
					case 1 :
					    numOfLink = new Integer(tokens[j]).intValue();
						break;
					case 2 :
					    user = tokens[j];
						break;
					case 3 :
					    group = tokens[j];
						break;
					case 4 :
					    filesize = new Integer(tokens[j]).intValue();
						break;
					case 5 :
						month = tokens[j];
						if ("Jan".equals(month)) {
						    month = new Integer(Calendar.JANUARY).toString(); 
						    
						} else if ("Feb".equals(month)) {
						    month = new Integer(Calendar.FEBRUARY).toString();
						} else if ("Mar".equals(month)) {
						    month = new Integer(Calendar.MARCH).toString();
						} else if ("Apr".equals(month)) {
						    month = new Integer(Calendar.APRIL).toString();
						} else if ("May".equals(month)) {
						    month = new Integer(Calendar.MAY).toString();
						} else if ("Jun".equals(month)) {
						    month = new Integer(Calendar.JUNE).toString();
						} else if ("Jul".equals(month)) {
						    month = new Integer(Calendar.JULY).toString();
						} else if ("Aug".equals(month)) {
						    month = new Integer(Calendar.AUGUST).toString();
						} else if ("Sep".equals(month)) {
						    month = new Integer(Calendar.SEPTEMBER).toString();
						} else if ("Oct".equals(month)) {
						    month = new Integer(Calendar.OCTOBER).toString();
						} else if ("Nov".equals(month)) {
						    month = new Integer(Calendar.NOVEMBER).toString();
						} else if ("Dec".equals(month)) {
						    month = new Integer(Calendar.DECEMBER).toString();
						}
						break;	
					case 6 :
						day = tokens[j];
						break;
					case 7 :
						if (tokens[j].indexOf(":") == -1)
							year = tokens[j];
						else
							time = tokens[j];
					    if ("".equals(year)) {
					    	Calendar calendar = Calendar.getInstance();
					        year = String.valueOf(calendar.get(Calendar.YEAR));
					    } else {
					        time = "00:00";
					    }
					    String[] _time = time.split(":"); 
					    Calendar calendar = Calendar.getInstance();
					    calendar.set(new Integer(year).intValue(), new Integer(month).intValue(), new Integer(day).intValue(), new Integer(_time[0]).intValue(), new Integer(_time[1]).intValue(), 0);
					    lastUpdate = calendar.getTimeInMillis();
						break;
					case 8 :
				        filename = tokens[j];
					    break;
					default : continue;
				}
			}
			RemoteFileEntry remoteFileEntry = null;
			if (realPath != null && isListingDir) {
				if (realPath.endsWith("/")) {
				    remoteFileEntry = new RemoteFileEntry(realPath + filename);
				} else {
				    remoteFileEntry = new RemoteFileEntry(realPath + "/" + filename);
				}
			} else
			    remoteFileEntry = new RemoteFileEntry(filename);
			remoteFileEntry.setFileSize(filesize);
			remoteFileEntry.setDir(isDir);
			remoteFileEntry.setLastModified(lastUpdate);
			remoteFileEntry.setLink(isLink);
//			fileList.add(remoteFileEntry);    
//		}
		return remoteFileEntry;
    }	
	/**
	 * 
	 */
	public OpenSSHDirListParser() {
		super();
	}
	
	public void init(InetAddress connectedAddress, int connectedPort, SFTPAuthInfo connectedAuthInfo) {
		this.connectedAddress = connectedAddress;
		this.connectedPort = connectedPort;
		this.connectedAuthInfo = connectedAuthInfo;
		
		String username = connectedAuthInfo.getUsername();
		String address = connectedAddress.getHostAddress();
		File keyFile = connectedAuthInfo.getSshKeyFile();
		char[] passphrase = connectedAuthInfo.getPassphrase();
		
		passwordLogin = (keyFile==null);
		
		if (passwordLogin) {
			cmdStringBase = sshCommand+" -p "+connectedPort+" "+username+"@"+address+" "+lsCommand+" "+lsOptions+" ";
		} else {
			cmdStringBase = sshCommand+" -p "+connectedPort+" -i "+keyFile.getAbsolutePath()+" "+username+"@"+address+" "+lsCommand+" "+lsOptions+" ";
		}
	}
	
	private int parseErrLine(String line) {
		if (line.indexOf(PASSWORD_PROMPT) >= 0) {
			return PASSWORD_PROMPT_BELOW;
		} else if (line.indexOf(FINGERPRINT_PROMPT) >=0) {
			return FINGERPRINT_PROMPT_BELOW;
		} else if (line.indexOf(FILE_NOT_FOUND_PROMPT) >=0) {
			return FILE_NOT_FOUND_PROMPT_BELOW;
		} else if (line.indexOf(HOST_NOT_FOUND_PROMPT) >=0) {
			return HOST_NOT_FOUND_PROMPT_BELOW;
		} else {
			return UNKNOWN_LINE;
		}
		
	}
	
	public synchronized List getDirList(RemoteFileEntry path) 
		throws SFTPRemoteFileNotFoundException, SFTPRemoteIOException, SFTPIOException, SFTPException 
	{
		List results = null;
		InputStream is = null;
		InputStream err = null;
		OutputStream os = null;
		
		try {
			byte[] buffer = new byte[1024];
			String realpath ;
			if (path==null) realpath = "";
			else realpath = path.getRemoteFile();
			Process proc = Runtime.getRuntime().exec(cmdStringBase+realpath);
//System.err.println("excuting "+cmdStringBase+realpath);
			is = proc.getInputStream();
			BufferedReader bis = new BufferedReader(new InputStreamReader(is));
			err = proc.getErrorStream();
			BufferedReader berr = new BufferedReader(new InputStreamReader(err));
			os = proc.getOutputStream();
			PrintStream ps = new PrintStream(os);
			
			//ReadLsResultThread readThread = new ReadLsResultThread(bis);
			//readThread.startProcess();
			
			String errline = null;
			
			while ((errline = berr.readLine())!=null) {
				int errLineMode = parseErrLine(errline);
				if (errLineMode == PASSWORD_PROMPT_BELOW) {
//System.err.println("password prompted");
					
					if (passwordLogin) {
						throw new SFTPAuthException(SFTPAuthException.CODE_RUNTIME, "Authentication Fail - Password login is not supported.");
//						ps.println(this.connectedAuthInfo.getPassphrase());
//						ps.flush();
					} else {
						throw new SFTPAuthException(SFTPAuthException.CODE_RUNTIME, "Authentication Fail - Password is not available.");
					}
				} else if (errLineMode == FINGERPRINT_PROMPT_BELOW) {
//System.err.println("fingerprint prompted");
					ps.println("yes");
				} else if (errLineMode == FILE_NOT_FOUND_PROMPT_BELOW) {
					throw new SFTPRemoteFileNotFoundException(SFTPRemoteFileNotFoundException.CODE_RUNTIME, "File Not Found - "+realpath);
				} else if (errLineMode == HOST_NOT_FOUND_PROMPT_BELOW) {
					throw new SFTPIOException(SFTPIOException.CODE_RUNTIME, "Host not found");
				} else {
					//ignore
//System.err.println("Unknown error line : "+errline);
				}
			}
//System.err.println("leaving err loop");
			
			//readThread.waitFor();
			String dirLine = null;
			boolean isListingDir = false;
			results = new ArrayList();
			while ((dirLine = bis.readLine())!=null) {
				if (dirLine.startsWith("total")) {
					isListingDir = true;
				}
				RemoteFileEntry entry = parseLs(realpath,dirLine,isListingDir);
				if (entry!=null) results.add(entry);				
//System.err.println(dirLine);
			}
//for (int i=0;i<results.size();i++) {
//	System.err.println(results.get(i));
//}
			
			return results;
		} catch (SFTPException sftpe) {
			throw sftpe;
		} catch (IOException ioe) {
			throw new SFTPException(SFTPException.CODE_RUNTIME,"Error when getting list ",ioe);
			
		} finally {
			if (os!=null) try { os.close(); } catch (Exception e){}
			if (is!=null) try { is.close(); } catch (Exception e){}
			if (err!=null) try { err.close(); } catch (Exception e){}
		}
	}

	public static void main(String[] args)
		throws Exception
	{
		JPasswordField passwordField = (JPasswordField) new JPasswordField(20);
		
		if (args.length==0) {
			System.out.println("Usage: java com.pccw.oframework.sftp.SFTPConnectionFactory <connectionString> [ --keyFile <keyFile> ] [ --dir <dir>]" );
			System.out.println("  connectionString : userid@host:port");
			return;
		}
		String connectString = args[0];
		String dir = null;
		File keyFile = null; 
		char[] passwd = null;
		
		for (int i=1; i<args.length; i++) {
			if (args[i].trim().equals("--keyFile")) {
				keyFile = new File(args[++i]);
			}
			if (args[i].trim().equals("--dir")) {
				dir = args[++i];
			}
		}
		
		Object[] ob = { passwordField };
		int result = JOptionPane.showConfirmDialog(null, ob, "Please input the password",
				JOptionPane.OK_CANCEL_OPTION);
		if (result == JOptionPane.OK_OPTION) {
			passwd = passwordField.getPassword();
		} else {
		}
		
		
		
		int atIdx = connectString.indexOf("@");
		String userid = connectString.substring(0,atIdx);
		
		int colonIdx = connectString.indexOf(":");
		String host = null;
		int port = 22;
		
		if (colonIdx>0) {
			host = connectString.substring(atIdx+1,colonIdx);
			port = Integer.parseInt(connectString.substring(colonIdx+1));
		} else {
			host = connectString.substring(atIdx+1);
		}
		
		SFTPAuthInfo auth = new SFTPAuthInfo(userid,keyFile,passwd);

		OpenSSHDirListParser p = new OpenSSHDirListParser();
		
		p.setSshCommand("c:\\cygwin\\bin\\ssh.exe");
		p.init(InetAddress.getByName(host),port,auth);
		
		List dirList = p.getDirList(new RemoteFileEntry(dir));
		for (int i=0;i<dirList.size();i++) {
			System.out.println(dirList.get(i));
		}
		
	}
	
	public String getLsCommand() {
		return lsCommand;
	}
	public String getLsOptions() {
		return lsOptions;
	}
	public void setLsCommand(String lsCommand) {
		this.lsCommand = lsCommand;
	}
	public void setLsOptions(String lsOptions) {
		if (lsOptions==null)this.lsOptions="";
		else this.lsOptions = lsOptions;
	}
	public String getSshCommand() {
		return sshCommand;
	}
	public void setSshCommand(String sshCommand) {
		this.sshCommand = sshCommand;
	}
	
	class ReadLsResultThread implements Runnable {
		BufferedReader bis;
		Object lock = new Object();
		Thread runningThread = null;
		
		public ReadLsResultThread(BufferedReader _bis) {
			bis = _bis;
		}
		
		public void run() {
			try {
				synchronized (lock) {
System.err.println("start reading ls loop");
					String dirLine = null;
					while ((dirLine = bis.readLine())!=null) {
						System.out.println(dirLine);
					}
System.err.println("end reading ls loop");
				}
			} catch (Exception e) {
				
			} finally {
				runningThread = null;				
			}
		}
		
		public void startProcess() {
			if (runningThread==null) {
				runningThread = new Thread(this);
				runningThread.start();
			}
		}
		
		public void waitFor() {
			synchronized (lock) {
				return;
			}
		}
		
	}
}
